package
{
	import __AS3__.vec.Vector;
	
	import flash.data.SQLConnection;
	import flash.data.SQLResult;
	import flash.data.SQLStatement;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.SQLEvent;
	import flash.filesystem.File;
	import flash.utils.Dictionary;
	
	import mx.collections.ArrayCollection;
	import mx.collections.Sort;
	import mx.collections.SortField;
	import mx.controls.Image;
	import mx.graphics.codec.PNGEncoder;
	
	import photoEffects.PhotoEffectUtil;

	/**
	 * This is a class implemented in singleton pattern to handle all the in/out of Photo to the SQLite database.
	 */
	public class PhotoStorage extends EventDispatcher
	{
		/**
		 * Note that isPhotoLoaded should be checked before accessing this to make sure the variable is most updated.
		 */ 
		[Bindable]
		public var allPhotos:ArrayCollection = new ArrayCollection();
		
		/**
		 * To see if any changes is made to the database and the allPhotos is updated according to the changes.<br />
		 * reloadPhoto() may be called if it returns false.
		 */
		public function get isPhotoLoaded():Boolean {
			return _photoLoaded;
		}
		
		public function PhotoStorage()
		{
			if( instance ) throw new Error( "PhotoStorage can only be accessed through PhotoStorage.getInstance()" );
			
			sqlConn.open(dbFile);
			if(isNewDB){
				createDatabase();
			}
			
			stmtGetAll = new SQLStatement();
			stmtGetAll.sqlConnection = sqlConn;
			stmtGetAll.text = "SELECT * FROM photo";
			stmtGetAll.addEventListener(SQLEvent.RESULT,onGetAll);
			
			stmtInsert = new SQLStatement();
			stmtInsert.sqlConnection = sqlConn;
			stmtInsert.text =	"INSERT INTO photo (original, image, effects, dateCreated, tags) " + 
						 		"VALUES (:original, :image, :effects, :dateCreated, :tags)";
						 		
			stmtUpdate = new SQLStatement();
			stmtUpdate.sqlConnection = sqlConn;
			stmtUpdate.text =	"UPDATE photo SET " +
								"original = :original, " + 
								"image = :image, " + 
								"effects = :effects, " + 
								"dateCreated = :dateCreated, " + 
								"tags = :tags" + 
								"WHERE photo_id = :photo_id";
			
			stmtDelete = new SQLStatement();
            stmtDelete.sqlConnection = sqlConn;
            stmtDelete.text = "DELETE FROM photo WHERE photo_id = :photo_id";
            
            var photoSort:Sort = new Sort();
            photoSort.fields = [new SortField("id",true,true,Number)];
            allPhotos.sort = new Sort();
            
            reloadPhoto();
		}
		
		public static function getInstance():PhotoStorage {
			return instance;
		}
		
		/**
		 * Save the photo to the database. Old version of the Photo will be overwrited.
		 */
		public function save(photo:Photo):void {
			var pngEncoder:PNGEncoder = new PNGEncoder();
			numNotLoadedPhoto = 0;
			_photoLoaded = false;
			if (photo.id == -1){ //photo is not in db
	            stmtInsert.parameters[":original"] = pngEncoder.encode(photo.original.bitmapData.clone());
	            stmtInsert.parameters[":image"] = pngEncoder.encode(photo.image.bitmapData.clone()); 
	            stmtInsert.parameters[":effects"] = PhotoEffectUtil.dumpEffects(photo.effects);
	            stmtInsert.parameters[":dateCreated"] = photo.dateCreated.valueOf(); 
	            stmtInsert.parameters[":tags"] = photo.tags.toString();
	            stmtInsert.execute();
	            photo.id = stmtInsert.getResult().lastInsertRowID;
	            
	            
			} else { //photo is in db
				stmtUpdate.parameters[":original"] = pngEncoder.encode(photo.original.bitmapData.clone());
	            stmtInsert.parameters[":image"] = pngEncoder.encode(photo.image.bitmapData.clone()); 
	            stmtInsert.parameters[":effects"] = PhotoEffectUtil.dumpEffects(photo.effects); 
	            stmtInsert.parameters[":dateCreated"] = photo.dateCreated.valueOf(); 
	            stmtInsert.parameters[":tags"] = photo.tags.toString();
	            stmtInsert.parameters[":photo_id"] = photo.id.toString();
	            stmtInsert.execute();
			}
		}
		
		public function deletePhoto(photoID:int):void{
			stmtDelete.parameters[":photo_id"] = photoID;
			numNotLoadedPhoto = 0;
			_photoLoaded = false;
			stmtDelete.execute();
		}
		
		public function reloadPhoto(callback:Function = null):void{
			reloadPhotoCallbacks.push(callback);
			if (numNotLoadedPhoto<=0){
				allPhotos.removeAll();
				_photoLoaded = false;
				stmtGetAll.execute();
			}
		}
		
		public function getPhotoByID(photoID:Number):Photo {
			var photo:Photo;
			for (var i:Number = 0 ; i<allPhotos.length ; i++){
				photo = allPhotos[i] as Photo;
				if (photo.id == photoID) return photo;
			}
			return null;
		}
		
		//Private stuff start here
		private static var instance:PhotoStorage = new PhotoStorage();
		private var dbFile:File = File.applicationStorageDirectory.resolvePath("funPhotoTaking.db");
		private var isNewDB:Boolean = !dbFile.exists;
		private var sqlConn:SQLConnection = new SQLConnection();
		private var stmtGetAll:SQLStatement;
		private var getAllResultArray:Array;
		private var stmtInsert:SQLStatement;
		private var stmtUpdate:SQLStatement;
		private var stmtDelete:SQLStatement;
		private var dict:Dictionary = new Dictionary(true);
		private var reloadPhotoCallbacks:Vector.<Function> = new Vector.<Function>();
		private var numNotLoadedPhoto:uint = 0;
		[Bindable]
		private var _photoLoaded:Boolean = false;
		
		private function createDatabase():void
        {
            var stmt:SQLStatement = new SQLStatement();
            stmt.sqlConnection = sqlConn;
            stmt.text =	"CREATE TABLE photo (" + 
	            			"photo_id INTEGER PRIMARY KEY AUTOINCREMENT, " + 
			        		"original BLOB, " + 
			        		"image BLOB, " + 
			        		"effects TEXT, " + 
			        		"dateCreated REAL, " + 
			        		"tags TEXT" + 
			        	")";
            stmt.execute();
        }
        private function onGetAll(evt:SQLEvent):void {
        	var result:SQLResult = stmtGetAll.getResult();
        	if (result && result.data){
        		getAllResultArray = result.data;
        		var tempImage:Image = new Image();
        		numNotLoadedPhoto = getAllResultArray.length;
        		if (numNotLoadedPhoto > 0){
        			for (var i:int = 0 ; i < numNotLoadedPhoto ; i++){
	        			var l:Loader = new Loader();
	        			l.contentLoaderInfo.addEventListener(Event.COMPLETE,onOriginalBitmapLoaded);
	        			dict[l] = i;
	        			l.loadBytes(result.data[i]["original"]);
	        		}
        		} else {
        			numNotLoadedPhoto = 0;
	    			_photoLoaded = true;
	    			executeCallBacks(reloadPhotoCallbacks);
        		}
        	} else {
        		getAllResultArray = null;
        		numNotLoadedPhoto = 0;
    			_photoLoaded = true;
    			executeCallBacks(reloadPhotoCallbacks);
    		}
        }
        
        private function executeCallBacks(callbacks:Vector.<Function>):void {
       		var callback:Function;
        	while (callback = callbacks.pop()){
        		callback();
        	}
        }
        
        private function onOriginalBitmapLoaded(evt:Event):void{
        	var loader:Loader = (evt.target as LoaderInfo).loader;
        	var resultIndex:int = dict[loader];
        	var bd:BitmapData = new BitmapData(loader.width,loader.height);
        	bd.draw(loader);
        	var ori:Bitmap = new Bitmap(bd);
        	var photo:Photo = new Photo(ori,new Date(getAllResultArray[resultIndex]["dateCreated"]));
			photo.id = getAllResultArray[resultIndex]["photo_id"];
			photo.tags = new ArrayCollection((getAllResultArray[resultIndex]["tags"] as String).split(','));
			
			//restore effects
			var effs:Array = (getAllResultArray[resultIndex]["effects"] as String).split(',');
			for (var e:int = 0 ; e < effs.length ; e++){
				var eff:String = effs[e];
				var effClass:Class = PhotoEffectUtil.getEffectClassByName(eff);
				if (effClass) {
					photo.effects.addItem(new effClass());
				}
			}
			
			allPhotos.addItem(photo);
			allPhotos.refresh();
			//trace("onOriginalBitmapLoaded", numNotLoadedPhoto)
			if (--numNotLoadedPhoto <= 0){
				numNotLoadedPhoto = getAllResultArray.length;
				for (var i:uint = 0 ; i < numNotLoadedPhoto ; i++){
        			var l2:Loader = new Loader();
	    			l2.contentLoaderInfo.addEventListener(Event.COMPLETE,onBitmapLoaded);
	    			dict[l2] = i;
	    			l2.loadBytes(getAllResultArray[i]["image"]);
        		}
			}
			loader.removeEventListener(Event.COMPLETE,onOriginalBitmapLoaded);
			dict[loader] = null;
        }
        
        private function onBitmapLoaded(evt:Event):void{
        	var loader:Loader = (evt.target as LoaderInfo).loader;
        	var resultIndex:int = dict[loader];
        	var bd:BitmapData = new BitmapData(loader.width,loader.height);
        	bd.draw(loader);
        	var img:Bitmap = new Bitmap(bd);
        	var photo:Photo = getPhotoByID(getAllResultArray[resultIndex]["photo_id"]);
			
			photo.setCachedImage(img);
			//trace("onBitmapLoaded", numNotLoadedPhoto)
			if (--numNotLoadedPhoto <= 0){
				_photoLoaded = true;
				executeCallBacks(reloadPhotoCallbacks);
			}
			loader.removeEventListener(Event.COMPLETE,onBitmapLoaded);
			dict[loader] = null;
        }
	}
}